package com.neusoft.bizcore.web.exception;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import com.neusoft.bizcore.web.dto.result.ResultDTO;
import com.neusoft.bizcore.web.dto.result.ResultError;
import com.neusoft.bizcore.web.utils.JsonUtils;

import lombok.extern.slf4j.Slf4j;

/**
 * handle rest exceptions
 *
 * @author sunchf
 * @date 2020年4月1日 下午1:38:46
 */
@Slf4j
@ControllerAdvice
public class RestResponseExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleExceptionInternal(final Exception ex, final Object body,
            final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
        final String requestDesc = this.getRequestDesc(request);
        RestResponseExceptionHandler.log.error(requestDesc, ex);
        return super.handleExceptionInternal(ex, body, headers, status, request);
    }

    /**
     * Exception to be thrown when validation on an argument annotated with
     * {@code @Valid} fails.
     */
    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(final MethodArgumentNotValidException ex,
            final HttpHeaders headers, final HttpStatus status, final WebRequest request) {

        final String requestDesc = this.getRequestDesc(request);
        RestResponseExceptionHandler.log.error(requestDesc, ex);

        final ArrayList<ResultError> errors = new ArrayList<>();
        ex.getBindingResult().getAllErrors().forEach(error -> {
            final String defaultMessage = error.getDefaultMessage();
            errors.add(new ResultError(defaultMessage, null));
        });
        ex.getBindingResult().getFieldErrors().forEach(error -> {
            final String defaultMessage = error.getDefaultMessage();
            final String field = error.getField();
            errors.add(new ResultError(defaultMessage, field));
        });

        final String data = JsonUtils.pojoToJson(errors);
        final ResultDTO<String> resultDTO = ResultDTO.failure(data);
        return new ResponseEntity<>(resultDTO, headers, HttpStatus.OK);
    }

    /**
     * constraint violation
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<Object> handleConstraintViolationException(final ConstraintViolationException ex,
            final HttpServletRequest request) {

        final String requestDesc = this.getRequestDesc(request);
        RestResponseExceptionHandler.log.error(requestDesc, ex);

        final Set<ConstraintViolation<?>> violations = ex.getConstraintViolations();
        final ResultError[] errors = new ResultError[violations.size()];
        final List<ResultError> errorList = violations.stream().map(v -> {
            final ResultError error = new ResultError();
            error.setField(v.getPropertyPath().toString());
            error.setErrmsg(v.getMessage());
            return error;
        }).collect(Collectors.toList());
        for (int i = 0; i < errorList.size(); i++) {
            errors[i] = errorList.get(i);
        }

        final HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);

        final ResultDTO<String> resultDTO = ResultDTO.failure(JsonUtils.pojoToJson(errors));
        return new ResponseEntity<>(resultDTO, headers, HttpStatus.OK);
    }

    /**
     * that indicates a missing parameter.
     */
    @Override
    protected ResponseEntity<Object> handleMissingServletRequestParameter(
            final MissingServletRequestParameterException ex, final HttpHeaders headers, final HttpStatus status,
            final WebRequest request) {

        RestResponseExceptionHandler.log.error("", ex);

        final ResultDTO<String> resultDTO = ResultDTO.failure(ex.getMessage());
        return new ResponseEntity<>(resultDTO, headers, HttpStatus.OK);
    }

    /**
     * united exception
     *
     * @param ex
     * @param request
     * @return
     */
    @ExceptionHandler(UnitedException.class)
    public ResponseEntity<Object> exceptionHandler(final UnitedException ex, final HttpServletRequest request) {
        final String requestDesc = this.getRequestDesc(request);
        RestResponseExceptionHandler.log.error(requestDesc, ex.getCause());

        final HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        final ResultDTO<String> resultDTO = ResultDTO.failure(ex.getMessage());
        return new ResponseEntity<>(resultDTO, headers, HttpStatus.OK);
    }

    /**
     * disconnect exception
     *
     * @param ex
     * @param request
     * @return
     */
    @ExceptionHandler(DisconnectException.class)
    public ResponseEntity<Object> exceptionHandler(final DisconnectException ex, final HttpServletRequest request) {
        final String requestDesc = this.getRequestDesc(request);
        RestResponseExceptionHandler.log.error(requestDesc, ex.getCause());

        final HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        final ResultDTO<String> resultDTO = ResultDTO.failure(ex.getMessage());
        return new ResponseEntity<>(resultDTO, headers, HttpStatus.OK);
    }

    /**
     * config error exception
     *
     * @param ex
     * @param request
     * @return
     */
    @ExceptionHandler(ConfigErrorException.class)
    public ResponseEntity<Object> exceptionHandler(final ConfigErrorException ex, final HttpServletRequest request) {
        final String requestDesc = this.getRequestDesc(request);
        RestResponseExceptionHandler.log.error(requestDesc, ex.getCause());

        final HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        final ResultDTO<String> resultDTO = ResultDTO.failure(ex.getMessage());
        return new ResponseEntity<>(resultDTO, headers, HttpStatus.OK);
    }

    /**
     * retryable exception
     *
     * @param ex
     * @param request
     * @return
     */
    @ExceptionHandler(RetryableException.class)
    public ResponseEntity<Object> exceptionHandler(final RetryableException ex, final HttpServletRequest request) {
        final String requestDesc = this.getRequestDesc(request);
        RestResponseExceptionHandler.log.error(requestDesc, ex.getCause());

        final HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        final ResultDTO<String> resultDTO = ResultDTO.failure(ex.getMessage());
        return new ResponseEntity<>(resultDTO, headers, HttpStatus.OK);
    }

    @ExceptionHandler(AccessDeniedException.class)
    public ResponseEntity<Object> accessDeniedExceptionHandler(final AccessDeniedException ex,
            final HttpServletRequest request) {
        RestResponseExceptionHandler.log.error(ex.getMessage());

        final HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        final ResultDTO<String> resultDTO = ResultDTO.failure(ex.getMessage());
        return new ResponseEntity<>(resultDTO, headers, HttpStatus.FORBIDDEN);
    }

    /**
     * Insert或Update数据库时，违反了完整性，例如违反了唯一性约束
     * 
     * @param ex
     * @param request
     * @return
     */
    @ExceptionHandler(DataIntegrityViolationException.class)
    public ResponseEntity<Object> accessDeniedExceptionHandler(final DataIntegrityViolationException ex,
            final HttpServletRequest request) {

        String error = ex.getMessage();
        if (ex.getCause() instanceof org.hibernate.exception.ConstraintViolationException) {
            error = "操作失败，请检查是否重复设置";
        }
        RestResponseExceptionHandler.log.error(error);

        final HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        final ResultDTO<String> resultDTO = ResultDTO.failure(error);
        return new ResponseEntity<>(resultDTO, headers, HttpStatus.OK);
    }

    /**
     * other exception
     *
     * @param ex
     * @param request
     * @return
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Object> exceptionHandler(final Exception ex, final HttpServletRequest request) {

        final String requestDesc = this.getRequestDesc(request);
        RestResponseExceptionHandler.log.error(requestDesc, ex);

        final ResponseStatus statusAnnotation = ex.getClass().getAnnotation(ResponseStatus.class);
        if (statusAnnotation != null) {
            final HttpStatus status = statusAnnotation.value();
            return new ResponseEntity<>(ex.getMessage(), status);
        }
        final HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        final ResultDTO<String> resultDTO = ResultDTO.failure(ex.getMessage());
        return new ResponseEntity<>(resultDTO, headers, HttpStatus.OK);

    }

    private String getRequestDesc(final HttpServletRequest request) {
        final String method = request.getMethod();
        final String url = request.getRequestURL().toString();
        return String.format("%s %s", method.toUpperCase(), url);
    }

    private String getRequestDesc(final WebRequest request) {
        final String method = ((ServletWebRequest) request).getHttpMethod().toString();
        final String url = ((ServletWebRequest) request).getRequest().getRequestURL().toString();
        return String.format("%s %s", method.toUpperCase(), url);
    }

}
